//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

//== INCLUDES ========================================================================

#include "sagittarius/core/SagittariusCore.hpp"

#include "sagittarius/unicode/SimpleUnicodeChar.hpp"

namespace Sagittarius
{
    
    SimpleUnicodeChar SimpleUChar_FromStream(std::istream& is)
    {
        typedef SimpleUnicodeChar CodepointType;
        SimpleUnicodeChar codepoint;
        #if SAGITTARIUS_DEBUG_LEVEL >= 1
            SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
        #endif
        //clear codepoint
        codepoint = 0x00;
        int n = 1;
        char chr;
        is.get(chr);
        if(is.eof())
        {
            //SAGITTARIUS_ASSERT(false);
            throw SimpleUnicodeChar_EOF();
        }
        if(!(chr & 0x80))
        {
            //one byte
            codepoint = chr;
            //_n_bytes = 1;
            //std::cout << "SimpleUChar_FromStream: codepoint = " << SimpleUChar_toStdStr(codepoint) << "\n";
            return codepoint;
        }
        else
        {
            for(n = 2; n<=4; ++n)
            {
                if(!((chr << n) & 0x80))
                    break;
            }
        }
        if((chr << n) & 0x80)
            SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "SimpleUChar_FromStream");
        //_n_bytes = n;
        
        //get data
        //the one-byte case was dealt with above, so we may assume more than one byte
        int marker;
        switch(n)
        {
            case 2:
                marker = 6;
                break;
            case 3:
                marker = 12;
                break;
            case 4:
                marker = 18;
                break;
        }
        codepoint |= (CodepointType)(chr & (0xFF >> (n+1))) << marker;
        marker -= 6;
        for(int i=1; i<n; ++i)
        {
            is.get(chr);
            //continuation byte
            if((chr & 0xC0) != 0x80)
                SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "SimpleUChar_FromStream");
            //get data
            codepoint |= (CodepointType)(chr & 0x3F) << marker;
        }
        //std::cout << "SimpleUChar_FromStream: codepoint = " << SimpleUChar_toStdStr(codepoint) << "\n";
        return codepoint;
    }
    
    ///SimpleUChar_From_UTF_8_String
    const char* SimpleUChar_From_UTF_8_String(const char* start, const char* end_of_buffer, SimpleUnicodeChar& codepoint)
    {
        typedef SimpleUnicodeChar CodepointType;
        #if SAGITTARIUS_DEBUG_LEVEL >= 1
            SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds reversed");
            SAGITTARIUS_ASSERT(sizeof(char) == 1 && "char must be one byte wide for this method to work");
        #endif
        //clear codepoint
        codepoint = 0x00;
        int n = 1;
        if(!(*start & 0x80))
        {
            //one byte
            codepoint = *start;
            ++start;
            //_n_bytes = 1;
            return start;
        }
        else
        {
            for(n = 2; n<=4; ++n)
            {
                if(!((*start << n) & 0x80))
                {
                    break;
                }
            }
        }
        if((*start << n) & 0x80)
            SAGITTARIUS_THROW(SanityCheckException, "Unrecognized start byte (possibly encoding for forbidden five or six-byte sequence)", "SimpleUChar_From_UTF_8_String");
        //_n_bytes = n;
        
        //get data
        //the one-byte case was dealt with above, so we may assume more than one byte
        int marker;
        switch(n)
        {
            case 2:
                marker = 6;
                break;
            case 3:
                marker = 12;
                break;
            case 4:
                marker = 18;
                break;
        }
        codepoint |= (CodepointType)(*start & (0xFF >> (n+1))) << marker;
        marker -= 6;
        ++start;
        for(int i=1; i<n; ++i)
        {
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            //continuation byte
            if((*start & 0xC0) != 0x80)
                SAGITTARIUS_THROW(SanityCheckException, "Expected continuation byte", "SimpleUChar_From_UTF_8_String");
            //get data
            codepoint |= (CodepointType)(*start & 0x3F) << marker;
            ++start;
        }
        return start;
    }
    
    ///SimpleUChar_nBytes
    int SimpleUChar_nBytes(SimpleUnicodeChar chr)
    {
        if(chr <= 0x007F)
            return 1;
        else if(chr <= 0x07FF)
            return 2;
        else if(chr <= 0xFFFF)
            return 3;
        else if(chr <= 0x1FFFFF)
            return 4;
        else if(chr <= 0x3FFFFFF)
            return 5;
        else if(chr <= 0x7FFFFFFF)
            return 6;
        else
            SAGITTARIUS_ASSERT(false && "Unrecognized codepoint");
    }
    
    ///SimpleUChar_toCStr
    char* SimpleUChar_toCStr(SimpleUnicodeChar codepoint, char* start, char* end_of_buffer)
    {
        int n_bytes = SimpleUChar_nBytes(codepoint);
        if(n_bytes == 1)
        {
            *start = (char)(codepoint & 0x7F);
        }
        else if(n_bytes == 2)
        {
            *start = (char)((codepoint >> 6) & 0x1F) | 0xC0;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)(codepoint & 0x3F) | 0x80;
        }
        else if(n_bytes == 3)
        {
            *start = (char)((codepoint >> 12) & 0x0F) | 0xE0;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)((codepoint >> 6) & 0x3F) | 0x80;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)(codepoint & 0x3F) | 0x80;
        }
        else if(n_bytes == 4)
        {
            *start = (char)((codepoint >> 18) & 0x07) | 0xF0;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)((codepoint >> 12) & 0x3F) | 0x80;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)((codepoint >> 6) & 0x3F) | 0x80;
            ++start;
            #if SAGITTARIUS_DEBUG_LEVEL >= 1
                SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
            #endif
            *start = (char)(codepoint & 0x3F) | 0x80;
        }
        else
            SAGITTARIUS_ASSERT(false);
        ++start;
        #if SAGITTARIUS_DEBUG_LEVEL >= 1
            SAGITTARIUS_ASSERT(start < end_of_buffer && "bounds check failed");
        #endif
        return start;
    }
    
    ///SimpleUChar_toStdStr
    std::string SimpleUChar_toStdStr(SimpleUnicodeChar codepoint)
    {
        int n_bytes = SimpleUChar_nBytes(codepoint);
        std::string result;
        if(n_bytes == 1)
        {
            result += (char)(codepoint & 0x7F);
        }
        else if(n_bytes == 2)
        {
            result += (char)((codepoint >> 6) & 0x1F) | 0xC0;
            result += (char)(codepoint & 0x3F) | 0x80;
        }
        else if(n_bytes == 3)
        {
            result += (char)((codepoint >> 12) & 0x0F) | 0xE0;
            result += (char)((codepoint >> 6) & 0x3F) | 0x80;
            result += (char)(codepoint & 0x3F) | 0x80;
        }
        else if(n_bytes == 4)
        {
            result += (char)((codepoint >> 18) & 0x07) | 0xF0;
            result += (char)((codepoint >> 12) & 0x3F) | 0x80;
            result += (char)((codepoint >> 6) & 0x3F) | 0x80;
            result += (char)(codepoint & 0x3F) | 0x80;
        }
        else
        {
            //SAGITTARIUS_ASSERT(false);
            result = "\\0";
        }
        return result;
    }
    
    ///SimpleUChar_ToCodepointString
    //convert to std::string
    std::string SimpleUChar_ToCodepointString(SimpleUnicodeChar codepoint)
    {
        int n_bytes = SimpleUChar_nBytes(codepoint);
        std::stringstream s;
        s << "U+";
        if(n_bytes <= 3)
        {
            //width = 6 bytes
            s.width(4);
            s.fill('0');
            s << std::hex << codepoint;
        }
        else if(n_bytes == 4)
        {
            //width = 8 bytes
            s.width(6);
            s.fill('0');
            s << std::hex << codepoint;
        }
        else
            SAGITTARIUS_ASSERT(false);
        return s.rdbuf()->str();
    }

} //namespace Sagittarius
